-
Notifications
You must be signed in to change notification settings - Fork 239
chore: add storybook-screen-reader plugin #5911
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
📚 Branch Preview Links🔍 First Generation Visual Regression Test ResultsWhen a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:
Deployed to Azure Blob Storage: If the changes are expected, update the |
384201c to
736dcbb
Compare
9c470bc to
45aea25
Compare
2nd-gen/packages/swc/package.json
Outdated
| "playwright": "1.53.1", | ||
| "postcss": "8.5.6", | ||
| "postcss-preset-env": "10.4.0", | ||
| "query-selector-shadow-dom": "^1.0.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please lock all dependency versions 😄
| * Stop the screen reader | ||
| */ | ||
| stop(): void { | ||
| if (!this.isRunning && !this.storyDocument) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible you mean OR, not AND?
| if (!this.isRunning && !this.storyDocument) { | |
| if (!this.isRunning || !this.storyDocument) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and nit: if you do change this... we need a storyDocument cleanup at the end of this stop method.
The storyDocument reference is never set to null, which could prevent garbage collection 🤔
| ): string { | ||
| const announcements: Record<string, RoleAnnouncementFn> = { | ||
| link: () => { | ||
| const visited = element.matches(':visited') ? 'visited ' : ''; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eheh, this is ingenious, but I think it does not work... Browsers are super aggressive with their privacy so this will always return false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did not know!!!! :(
| /** | ||
| * Generate announcement for an element based on its role and state | ||
| */ | ||
| announceElement(element: Element | null): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have we considered adding an aria-hidden check to skip elements hidden from the accessibility tree? If I understand this correctly we are now announcing them?
| role: string, | ||
| name: string | ||
| ): string { | ||
| const announcements: Record<string, RoleAnnouncementFn> = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about the disabled state? Could we append something like , disabled if it has aria-disabled (I guess if it has disabled attribute it is probably not focusable so we don't need to worry about that part? maybe confirm)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you're right... if disabled then no focusable so we no worry but for aria-disabled i should add a skip
| * Generate announcement for an element based on its role and state | ||
| */ | ||
| announceElement(element: Element | null): void { | ||
| if (!element || element === this.lastAnnouncedElement) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm... State changes on the focused element aren't announced. For example, if a checkbox is toggled, it's the same element, so we're skipping it here.
We could add something along the lines of an announceStateChange(element, attribute) method that announces only the new state.
We'd need to update onMutation to call it for things like aria-checked, aria-expanded, aria-pressed (can't remember any other) changes on the focused element. BTW, I see that currently only aria-selected triggers a re-announcement (line 741).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed! We now have:
- Mutation observer for aria-checked, aria-pressed, aria-expanded, aria-selected - announces state changes directly
- Change event handler for elements with checked property (like sp-checkbox) - catches native form element patterns
Both paths announce just the state ("checked", "pressed", "expanded") without re-announcing the full element.
42d3efc to
752ddf3
Compare
A Storybook addon that simulates screen reader behavior, providing instant audio and visual feedback as you navigate components.
What it does
Voice Reader: Speaks announcements using Web Speech API
Text Reader: Displays announcements in the addon panel
Tab through your component → hear/see what a screen reader would announce
How it works
Tracks focus via focusin events
Computes accessible names using dom-accessibility-api (W3C AccName spec)
Maps elements to ARIA roles via aria-query
Announces: role, name, states (checked, expanded, disabled, etc.)
Implementation
This addon is a shared local package at
2nd-gen/packages/swc/.storybook/addons/screen-reader-addon/:(.ts/.tsx)esbuildcompiles source directlysp-switch,sp-theme,sp-textfield,sp-help-text,sp-field-label1st-genand2nd-genaria-query,dom-accessibility-api,query-selector-shadow-dom) are installed in the parent packageLimitations
This is a development aid, not a replacement for real screen reader testing.
Good for: Quick feedback on accessible names, roles, and states
Not for: Final accessibility sign-off (use VoiceOver/NVDA for that)
Further Reading
See RFC for discussion on ownership, maintenance, and where this should live long-term.
Testing
First gen:
Go here
Note: Check the same storybook screen reader addon here in second-gen though it's impossible to verify if it works the same cz second-gen has no focusable component :(